package appml

import (
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/kitfast/resml"
	"github.com/shirou/gopsutil/v3/process"
)

// 升级自身时，作为最后一个命令行参数传入来创建辅助升级的进程。
// 例如 self.exe arg1 arg2 ... upgrader
const upgrader_argv = "upgrader"

// 应用程序管理器
type AppManager struct {
	mu sync.Mutex

	exe     string          // 当前可执行文件的路径
	config  *Config         // 配置
	procmgr *ProcessManager // 进程管理器

	upgradable      atomic.Int32     // 是否可升级，大于 0 时不可升级
	upgradeLocal    resml.Storage    // 升级资源的本地存储
	upgradeClient   *resml.UrlClient // 升级的资源客户端
	uploadLogClient *resml.UrlClient // 上传的资源客户端

	etager resml.ETager

	processCheckTicker, upgradeTicker, uploadTicker *time.Ticker // 进程检查、升级、上传 的定时器
}

// 通过 HTTP 拉取配置来创建 AppManager。如果 interval 大于 0，则每隔 interval 拉取并更新一次配置
func NewAppManagerByHttpPull(configUri string, interval time.Duration, headers http.Header) (am *AppManager, err error) {
	cnf, err := ConfigLoadFromHttp(configUri, headers)
	if err != nil {
		return
	}
	am, err = NewAppManager(cnf)
	if err == nil && interval > 0 {
		go func() {
			for range time.Tick(interval) {
				if c, e := ConfigLoadFromHttp(configUri, headers); e == nil {
					am.SetConfig(c)
				}
			}
		}()
	}
	return
}

// 创建应用程序管理器
func NewAppManager(config *Config) (*AppManager, error) {
	exe, _ := os.Executable()
	m := &AppManager{exe: exe, etager: resml.NewSha256HexEtager()}
	if err := m.setConfig(config); err != nil {
		return nil, NewErr(runtime.Caller(0)).Err(err)
	}
	loc, err := resml.NewOsFileObjectStorage(filepath.Join(config.Backup, "_cache"), true, m.etager)
	if err != nil {
		return nil, NewErr(runtime.Caller(0)).Err(err)
	}
	m.upgradeLocal = loc
	m.upgradeClient = resml.NewUrlClient(loc).WithETagCreation(m.etager)
	m.uploadLogClient = resml.NewUrlClient(nil).WithETagCreation(m.etager)
	m.checkSelfUpgrade() // 如果是升级辅助进程，需在 reload 之前执行，升级完即退出
	if err = m.reload(); err != nil {
		return nil, NewErr(runtime.Caller(0)).Err(err)
	}
	return m, nil
}

// 关闭
func (m *AppManager) Close() error {
	if m.uploadTicker != nil {
		m.uploadTicker.Stop()
	}
	if m.upgradeTicker != nil {
		m.upgradeTicker.Stop()
	}
	if m.processCheckTicker != nil {
		m.processCheckTicker.Stop()
	}
	log.Print("AppManager closed")
	return nil
}

// 设置/更新配置
func (m *AppManager) SetConfig(config *Config) error {
	if err := m.setConfig(config); err != nil {
		return NewErr(runtime.Caller(0)).Err(err)
	}
	return m.reload()
}

// 设置配置
func (m *AppManager) setConfig(config *Config) error {
	if err := config.Validate(); err != nil {
		return NewErr(runtime.Caller(0)).Err(err).Msg("config validate error:")
	}
	m.config = config
	return nil
}

// 重载配置
func (m *AppManager) reload() error {
	log.Print("AppManager reloading ...")
	if m.procmgr == nil {
		m.procmgr = NewProcessManager()
	}
	procs, err := process.Processes() // 获取 OS 的进程列表
	if err != nil {
		return NewErr(runtime.Caller(0)).Err(err)
	}
	for _, app := range m.config.Apps {
		p, err := NewProcess(app.Name, app.Work, app.Process...)
		if err != nil {
			return NewErr(runtime.Caller(0)).Err(err)
		}
		p.FindAndBindFrom(procs) // 绑定进程
		if app.Name == m.config.Name {
			if !m.config.Multi && p.ExistsExcept(int32(os.Getpid())) {
				os.Exit(0) // 如果不支持多进程，并且已经在运行，则本进程（后来者）退出
			}
			continue // 本身不加入进程列表
		}
		log.Printf("AppManager set process %s", p.name)
		m.procmgr.SetProcess(p)
	}
	m.procmgr.DelProcessIfNotIn(m.config.AppNames()...)
	m.resetTicker()
	return nil
}

// 停止定时器
func (m *AppManager) resetTicker() {
	if m.upgradeTicker == nil {
		m.upgrade() // 立即执行一次升级
		m.upgradeTicker = time.NewTicker(time.Duration(m.config.Interval.Upgrade) * time.Millisecond)
		go m.upgradeTick()
		time.Sleep(time.Second) // 暂停1秒，让升级先开始，再开始检查进程
	} else {
		m.uploadTicker.Reset(time.Duration(m.config.Interval.Upgrade) * time.Millisecond)
	}
	if m.processCheckTicker == nil {
		m.processCheckTicker = time.NewTicker(time.Duration(m.config.Interval.Process) * time.Millisecond)
		go m.checkProcessesTick()
	} else {
		m.processCheckTicker.Reset(time.Duration(m.config.Interval.Process) * time.Millisecond)
	}
	if m.uploadTicker == nil {
		m.uploadTicker = time.NewTicker(time.Duration(m.config.Interval.Upload) * time.Millisecond)
		go m.uploadsTick()
	} else {
		m.uploadTicker.Reset(time.Duration(m.config.Interval.Upload) * time.Millisecond)
	}
}

// 检查是否升级自身
func (m *AppManager) checkSelfUpgrade() {
	if os.Args[len(os.Args)-1] != upgrader_argv {
		return
	}
	// 升级自身时，最后一个参数为 upgrader_argv 时，表示本进程是升级辅助进程
	if m.config.Wait > 0 { // 等待 config.Wait 后再执行升级
		time.Sleep(time.Duration(m.config.Wait) * time.Millisecond)
	}
	m.upgradeSelfNow() // 马上升级自己
	os.Exit(0)         // upgradeSelfNow 会退出，此处兜底
}
